<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="JyrTq" id="JyrTq"><span data-lake-id="u94319978" id="u94319978" style="color: rgb(55, 65, 81)">典型回答</span></h1>
  <p data-lake-id="u440445c0" id="u440445c0"><br></p>
  <p data-lake-id="ubd610561" id="ubd610561"><span data-lake-id="u0a917f5a" id="u0a917f5a">如果应用占用的内存在不断地增长，但是堆和元空间等区域都没有明显变化，那么大概率可能和堆外内存的使用有关。所以我们应该考虑有哪些情况会使用到堆外内存。</span></p>
  <p data-lake-id="ud810e72d" id="ud810e72d"><br></p>
  <p data-lake-id="u71cdc83e" id="u71cdc83e"><span data-lake-id="ueb3bb461" id="ueb3bb461">那么，有哪些场景会使用到堆外内存呢？</span></p>
  <p data-lake-id="u6739101a" id="u6739101a"><span data-lake-id="ud875c6a1" id="ud875c6a1">​</span><br></p>
  <h3 data-lake-id="au5OU" id="au5OU"><span data-lake-id="u36b0676c" id="u36b0676c">ByteBuffer未及时回收</span></h3>
  <p data-lake-id="u4631d1aa" id="u4631d1aa"><span data-lake-id="ua25307b9" id="ua25307b9">​</span><br></p>
  <p data-lake-id="ue8b07d53" id="ue8b07d53"><span data-lake-id="uec7ca44a" id="uec7ca44a">Java NIO的ByteBuffer.allocateDirect在使用直接内存时，如果没有正确释放或及时回收，可能导致内存泄露。</span></p>
  <p data-lake-id="ua4faba18" id="ua4faba18"><span data-lake-id="ud7662737" id="ud7662737">​</span><br></p>
  <p data-lake-id="u0ca2d742" id="u0ca2d742"><span data-lake-id="u2064bbd7" id="u2064bbd7">​</span><br></p>
  <p data-lake-id="u13677ca0" id="u13677ca0"><span data-lake-id="uca43b95a" id="uca43b95a">当我们使用</span><code data-lake-id="uf1a77f33" id="uf1a77f33"><span data-lake-id="uc2e6c6d0" id="uc2e6c6d0">ByteBuffer buffer = ByteBuffer.allocateDirect(1024)</span></code><span data-lake-id="ucfcd97c2" id="ucfcd97c2">分配内存时，会在堆外占用1k的内存，同时会在堆上创建一个ByteBuffer对象，当然这个对象只占用一个对象的指针引用的大小。</span></p>
  <p data-lake-id="u07ac2acc" id="u07ac2acc"><span data-lake-id="u5b4ba137" id="u5b4ba137">​</span><br></p>
  <p data-lake-id="ue6796b6b" id="ue6796b6b"><span data-lake-id="u927d0806" id="u927d0806">当我们在做堆内存分析的时候，如果发现堆上有大量的ByteBuffer对象，虽然他们自身占用的内存空间很小，但是他们关联的堆外内存可能会很大。所以这时候就需要考虑ByteBuffer导致的内存泄露的问题。</span></p>
  <p data-lake-id="udbdfadab" id="udbdfadab"><br></p>
  <h3 data-lake-id="aGIEq" id="aGIEq"><span data-lake-id="u270d22bf" id="u270d22bf">堆外缓存使用不当</span></h3>
  <p data-lake-id="u47b189d8" id="u47b189d8"><br></p>
  <p data-lake-id="ueff75a90" id="ueff75a90"><span data-lake-id="uf530a05b" id="uf530a05b">除了NIO会使用堆外内存，很多缓存框架也会用堆外内存的，如Ehcache、MapDB、OHC等，他们都会使用堆外内存来提升存储空间以及减少对GC的影响。</span></p>
  <p data-lake-id="u6a1d44b2" id="u6a1d44b2"><span data-lake-id="u6e5e960d" id="u6e5e960d">​</span><br></p>
  <p data-lake-id="udee8d13d" id="udee8d13d"><span data-lake-id="u45316dab" id="u45316dab">我们都知道，这些缓存是有一些缓存过期策略的，但是如果没有设置合理，那么就可能导致长期无法被清理，大量占用堆外内存。</span></p>
  <p data-lake-id="ud4fb5946" id="ud4fb5946"><span data-lake-id="u41eeeb3f" id="u41eeeb3f">​</span><br></p>
  <h3 data-lake-id="vBerq" id="vBerq"><span data-lake-id="u931017b5" id="u931017b5" style="color: var(--tw-prose-bold)">日志文件的内存映射</span><span data-lake-id="ud0065363" id="ud0065363" style="color: rgb(55, 65, 81)">​</span></h3>
  <p data-lake-id="uf97cb4d8" id="uf97cb4d8"><span data-lake-id="u77f6109e" id="u77f6109e">​</span><br></p>
  <p data-lake-id="u0207d29f" id="u0207d29f"><span data-lake-id="ubb012fd1" id="ubb012fd1">现在， 很多日志框架都支持Memory Mapped File Appender（内存映射）这种日志记录方式，它利用了操作系统的内存映射文件特性来实现日志写入。这种方法通过将文件内容映射到进程的地址空间，允许应用程序像访问内存一样直接访问文件内容，从而提高了写入性能。</span></p>
  <p data-lake-id="u64c66e14" id="u64c66e14"><span data-lake-id="u36aee488" id="u36aee488">​</span><br></p>
  <p data-lake-id="u5bf856dc" id="u5bf856dc"><span data-lake-id="u5b58051e" id="u5b58051e">在使用内存映射记录日志时，它将日志先写入到映射到内存的文件中。这个过程利用内存映射文件的高效性，减少了实际的磁盘I/O操作。然后再由操作系统定时将内存中的更改同步回磁盘文件。</span></p>
  <p data-lake-id="u54789c82" id="u54789c82"><span data-lake-id="u0ec19623" id="u0ec19623">​</span><br></p>
  <p data-lake-id="u6b20df8f" id="u6b20df8f"><span data-lake-id="u998dd17e" id="u998dd17e">而这部分使用的内存就是堆外内存。随着日志文件的增长，映射到内存中的大小也会增长。所以会导致我们看到的应用占用的内存的增长。</span></p>
  <p data-lake-id="u6b9c5e58" id="u6b9c5e58"><br></p>
  <p data-lake-id="u6e130177" id="u6e130177"><span data-lake-id="u619eb3a4" id="u619eb3a4">还有需要注意的是，除了我们应用程序自己的日志，很多框架或者web容器也会有自己的日志缓存的机制。</span></p>
  <p data-lake-id="u08c54b67" id="u08c54b67"><br></p>
  <h3 data-lake-id="HgF14" id="HgF14"><span data-lake-id="u0874c500" id="u0874c500" style="color: var(--tw-prose-headings)">JNI和本地代码</span></h3>
  <p data-lake-id="u261ddc05" id="u261ddc05"><br></p>
  <p data-lake-id="u6cf81b64" id="u6cf81b64"><span data-lake-id="uccdbe184" id="uccdbe184">如果应用程序通过Java本地接口（JNI）调用本地代码或库，那么这些本地操作可能会分配额外的内存，而这部分内存不会在JVM堆内存或元空间中反映出来。所以，也需要检查一下程序中是不是有这种代码。</span></p>
  <p data-lake-id="ub5e7b338" id="ub5e7b338"><span data-lake-id="ufa29c58d" id="ufa29c58d">​</span><br></p>
  <h3 data-lake-id="QW1Gi" id="QW1Gi"><span data-lake-id="u2e5bd94b" id="u2e5bd94b">线程栈</span></h3>
  <p data-lake-id="ud7c69c0a" id="ud7c69c0a"><span data-lake-id="ucb3d0461" id="ucb3d0461">​</span><br></p>
  <p data-lake-id="u90b35d66" id="u90b35d66"><span data-lake-id="u17d661ac" id="u17d661ac">除了堆内内存、堆外内存，还需要考虑一下栈内存，每个线程都有自己的线程栈，随着线程数量的增加，线程栈所占用的内存也会增加。这方面也需要考虑一下。</span></p>
  <p data-lake-id="u41eedadd" id="u41eedadd"><span data-lake-id="uf1bd6bf9" id="uf1bd6bf9" class="lake-fontsize-12" style="color: rgb(55, 65, 81)">​</span><br></p>
  <p data-lake-id="u7d3b112c" id="u7d3b112c"><br></p>
 </body>
</html>